{
 "cells": [
  {
   "cell_type": "code",
   "execution_count": 5,
   "id": "16cf2da3-726e-44f6-a8a0-c64fd4cf187d",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/html": [
       "<table border=\"1\" class=\"dataframe\">\n",
       "  <thead>\n",
       "    <tr style=\"text-align: right;\">\n",
       "      <th></th>\n",
       "      <th>label</th>\n",
       "      <th>text</th>\n",
       "    </tr>\n",
       "  </thead>\n",
       "  <tbody>\n",
       "    <tr>\n",
       "      <th>0</th>\n",
       "      <td>3 stars</td>\n",
       "      <td>If you go to Cave Creek you must go to the Buffalo Chip, Harold's , hideaway etc.  just great local restaurants in a great town north of Phoenix/Scottsdale.   I like the Chip more for drinking dancing and rodeo than the food. But it is okay for a little grub   But it is fun.  So try it</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>1</th>\n",
       "      <td>3 stars</td>\n",
       "      <td>Chuy's was pretty good.  Seems like all of their locations are pretty similar.  I think they'd be best known for their cheap margaritas... $2 pints, $4 small pitchers.  They aren't too strong and come from a mediocre mix, but if you're looking for something sweet and cheap its a good bet.\\n\\nFree serve-yourself chips and salsa are pretty good.  \\n\\nAs for food... pretty decent prices and okay to good food.  I wouldn't say its authentic Mexican, actually I think the menu is a bit confused.  But the food is good overall.  It's a place we'll go to every month or so.</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>2</th>\n",
       "      <td>3 stars</td>\n",
       "      <td>I ended up eating at Taggia while staying at Firesky so it was a choice of convenience. I've had the food from here several times using room service and it's never anything to complain about. It was the same story the day I had lunch here. I had an organic greens salad and shared the margherita and goat cheese pizzas with my fellow lunchers. All of the food was good - the goat cheese pizza in particular with its thin, crispy crust.\\n\\nUnfortunately the day we ate here our service was MIA. We were told we could seat ourselves so we did. After about 10 minutes someone came by to take our drink order and maybe 10 minutes later our waters arrived. Well 2 out of 3 of them did anyway. Then we ordered two salads and two pizzas to share. One pizza came first. WTH? Where were the salads? Or the other pizza? The salads showed up a few minutes later and then our server realized that she had forgotten our second pizza. No biggie since we had salads and one pizza to eat. But the service was lackluster with a L. Like Andrea R says, I wouldn't go out of my way to eat here, but when in the area it's a good option to have.</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>3</th>\n",
       "      <td>2 star</td>\n",
       "      <td>I recently had a work luncheon at Ricardo's, I had been before years ago and it was extremely unmemorable. This visit would be more memorable but for the wrong reasons. \\n\\nWhen given the choice, I prefer to order off the menu than choose a buffet. But the whole group went to the buffet and I didn't want to be the oddball. I had two carne asada tacos, cheese enchilada and chips &amp; salsa. The enchilada was bland the only hint of flavor was the acidity from the tomatoes. The salsa, too, was bland and watery. The chips were pretty generic. The first taco was ok, a bit bland, but tender. The second was filled with grizzly meat. It really turned my stomach. Fortunately, the service was friendly and they were able to accomodate our large group.</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>4</th>\n",
       "      <td>4 stars</td>\n",
       "      <td>We had a great time at this resort over the long weekend.  The staff was super friendly, especially Adam, David and Cassie.  Great job!!!  And our suite was perfect to accommodate three women with lots of bags, make-up and shoes.  The Hole in the Wall restaurant had a really good breakfast, friendly staff and an outdoors patio.  Not so for the Rico Restaurant.  They were a bit rude, overwhelmed and obviously didn't want our business.  We also floated down the Lazy River, it was definitely Lazy...pretty slow but perfect temp.  All in all, I'll be back.</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>5</th>\n",
       "      <td>1 star</td>\n",
       "      <td>Im an owner with no kids, this place is not for my husband and I.. The element here is all about families and cooking in and playing in the pool from the moment it opens.\\n\\nThe restaurant bar is a bit of a joke, and the pressure to buy more points makes a relaxing vacation more stressful. We were an original owner and saw most of it built.\\n\\nWe noticed that they no longer offer a shuttle which is a mistake for those that want to go to the strip and not have to worry about driving. But after this weekend I see that they don't need to offer the shuttle because more than half the people there don't plan on leaving the facility at all.\\n\\nThe guests that we ran into all seemed to be there on a free vacation offer. They were tatted up and pushing baby strollers... and screaming to each other WAIT TIL YOU SEE THE POOL....\\n\\nMy hubby and myself both looked at each other and said OUT LOUD, we don't think we will be coming back here again ever to this location.\\n\\nWe came home and looked into selling  it all together, but then thought maybe we would try another location that Diamond Resorts has to offer before we do so..\\n\\nSo bottom line, if you have kids and love the pool and slides and pool some more.. this is for you.. If your looking for a weekend with the hubby or friends in Vegas to relax and to enjoy what Vegas is all about... this resort is not for you..</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>6</th>\n",
       "      <td>3 stars</td>\n",
       "      <td>Booked a room here through Priceline for the Tuesday before Thanksgiving.  Actually booked it on the drive in from Las Vegas through my cell phone, which was pretty sweet.  Paid $25 + tax, so you can't beat the price.  We had a hard time finding it as Google Maps was wrong about it's location, but you can't blame the hotel for that.\\n\\nWhat I can blame the hotel for is not giving me a king size bed.  Priceline had booked me with 2 doubles, and in my experience I am always able to switch unless they're sold out.  The front desk clerk told me they were indeed not sold out, but it was their policy not to let Priceline users switch rooms.  So much for considering staying there the rest of Thanksgiving week.\\n\\nI was going to let it go, but then at 9:30am the maid knocked on our door and woke me up 2 hours earlier than I had planned (we got to sleep at 5am, give me a break).  I ended up coming out of my room and saw that my do not disturb sign was on, so she must have chosen to ruin my day for fun.  Tried to fall back asleep but was then kept up by the sounds of what looked to be a loud garbage truck parked right outside of our room.\\n\\nI give up on sleeping.  At least they have solid free Internet so I can Yelp this hotel.  Courtyard, you're lucky you're getting 3 stars from me.</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>7</th>\n",
       "      <td>2 star</td>\n",
       "      <td>Been to 4 Cirques and this is the least favorite.  Sets and costumes are absolutely amazing but the acts were very unimpressive compared to the older ones we've seen.  \\nThe only exception was the opening act with the two twin men on ropes that swung out into the audience.  They weren't in the book at the shop so I think they added them in later to spice up the program. Breathtaking!\\n\\nPros- Art direction, sets, lights\\n\\nCons- Acts seen before and ANNOYING clowns</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>8</th>\n",
       "      <td>2 star</td>\n",
       "      <td>KOOLAID KID reminded me of home...nice touch..i know I know..this is suppose to be about the chicken &amp; waffles, but I must say quenching my thirst is very important to me..so back to the food..it was just that chicken(no flavor) &amp; waffles(nothing special)..mac &amp; cheese was very nice...and the new building was very very nice..okay that's all</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>9</th>\n",
       "      <td>1 star</td>\n",
       "      <td>Just called this location and I live 1.8 miles away. I asked them to deliver and they informed me that they would not deliver to my house because it was a couple hundred yards out of the map plan. They asked me to call the power and southern store. This store advised me that they could not deliver because jimmy johns has a two mile radius they can deliver to.  Called this store back and they once again decided to tell me even though I was in the two mile radius they did not want to deliver to me and my only option was for pickup. I will never eat at this location. I know the owners at Firehouse Subs and they go out of the way and this location is just lazy. Not getting my money jimmy johns no matter how fast you are. Laziness is worse</td>\n",
       "    </tr>\n",
       "  </tbody>\n",
       "</table>"
      ],
      "text/plain": [
       "<IPython.core.display.HTML object>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "Some weights of BertForSequenceClassification were not initialized from the model checkpoint at bert-base-cased and are newly initialized: ['classifier.bias', 'classifier.weight']\n",
      "You should probably TRAIN this model on a down-stream task to be able to use it for predictions and inference.\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "完整的超参数配置： TrainingArguments(\n",
      "_n_gpu=1,\n",
      "accelerator_config={'split_batches': False, 'dispatch_batches': None, 'even_batches': True, 'use_seedable_sampler': True, 'non_blocking': False, 'gradient_accumulation_kwargs': None, 'use_configured_state': False},\n",
      "adafactor=False,\n",
      "adam_beta1=0.9,\n",
      "adam_beta2=0.999,\n",
      "adam_epsilon=1e-08,\n",
      "auto_find_batch_size=False,\n",
      "average_tokens_across_devices=False,\n",
      "batch_eval_metrics=False,\n",
      "bf16=False,\n",
      "bf16_full_eval=False,\n",
      "data_seed=None,\n",
      "dataloader_drop_last=False,\n",
      "dataloader_num_workers=0,\n",
      "dataloader_persistent_workers=False,\n",
      "dataloader_pin_memory=True,\n",
      "dataloader_prefetch_factor=None,\n",
      "ddp_backend=None,\n",
      "ddp_broadcast_buffers=None,\n",
      "ddp_bucket_cap_mb=None,\n",
      "ddp_find_unused_parameters=None,\n",
      "ddp_timeout=1800,\n",
      "debug=[],\n",
      "deepspeed=None,\n",
      "disable_tqdm=False,\n",
      "do_eval=True,\n",
      "do_predict=False,\n",
      "do_train=False,\n",
      "eval_accumulation_steps=None,\n",
      "eval_delay=0,\n",
      "eval_do_concat_batches=True,\n",
      "eval_on_start=False,\n",
      "eval_steps=None,\n",
      "eval_strategy=epoch,\n",
      "eval_use_gather_object=False,\n",
      "fp16=False,\n",
      "fp16_backend=auto,\n",
      "fp16_full_eval=False,\n",
      "fp16_opt_level=O1,\n",
      "fsdp=[],\n",
      "fsdp_config={'min_num_params': 0, 'xla': False, 'xla_fsdp_v2': False, 'xla_fsdp_grad_ckpt': False},\n",
      "fsdp_min_num_params=0,\n",
      "fsdp_transformer_layer_cls_to_wrap=None,\n",
      "full_determinism=False,\n",
      "gradient_accumulation_steps=1,\n",
      "gradient_checkpointing=False,\n",
      "gradient_checkpointing_kwargs=None,\n",
      "greater_is_better=None,\n",
      "group_by_length=False,\n",
      "half_precision_backend=auto,\n",
      "hub_always_push=False,\n",
      "hub_model_id=None,\n",
      "hub_private_repo=None,\n",
      "hub_revision=None,\n",
      "hub_strategy=every_save,\n",
      "hub_token=<HUB_TOKEN>,\n",
      "ignore_data_skip=False,\n",
      "include_for_metrics=[],\n",
      "include_inputs_for_metrics=False,\n",
      "include_num_input_tokens_seen=False,\n",
      "include_tokens_per_second=False,\n",
      "jit_mode_eval=False,\n",
      "label_names=None,\n",
      "label_smoothing_factor=0.0,\n",
      "learning_rate=5e-05,\n",
      "length_column_name=length,\n",
      "liger_kernel_config=None,\n",
      "load_best_model_at_end=False,\n",
      "local_rank=0,\n",
      "log_level=passive,\n",
      "log_level_replica=warning,\n",
      "log_on_each_node=True,\n",
      "logging_dir=models/bert-base-cased-finetune-yelp/runs/Aug24_21-15-22_iZ6wearaq5de2lchqv8ap2Z,\n",
      "logging_first_step=False,\n",
      "logging_nan_inf_filter=True,\n",
      "logging_steps=30,\n",
      "logging_strategy=steps,\n",
      "lr_scheduler_kwargs={},\n",
      "lr_scheduler_type=linear,\n",
      "max_grad_norm=1.0,\n",
      "max_steps=-1,\n",
      "metric_for_best_model=None,\n",
      "mp_parameters=,\n",
      "neftune_noise_alpha=None,\n",
      "no_cuda=False,\n",
      "num_train_epochs=3,\n",
      "optim=adamw_torch,\n",
      "optim_args=None,\n",
      "optim_target_modules=None,\n",
      "output_dir=models/bert-base-cased-finetune-yelp,\n",
      "overwrite_output_dir=False,\n",
      "past_index=-1,\n",
      "per_device_eval_batch_size=8,\n",
      "per_device_train_batch_size=8,\n",
      "prediction_loss_only=False,\n",
      "push_to_hub=False,\n",
      "push_to_hub_model_id=None,\n",
      "push_to_hub_organization=None,\n",
      "push_to_hub_token=<PUSH_TO_HUB_TOKEN>,\n",
      "ray_scope=last,\n",
      "remove_unused_columns=True,\n",
      "report_to=['tensorboard'],\n",
      "restore_callback_states_from_checkpoint=False,\n",
      "resume_from_checkpoint=None,\n",
      "run_name=models/bert-base-cased-finetune-yelp,\n",
      "save_on_each_node=False,\n",
      "save_only_model=False,\n",
      "save_safetensors=True,\n",
      "save_steps=500,\n",
      "save_strategy=steps,\n",
      "save_total_limit=None,\n",
      "seed=42,\n",
      "skip_memory_metrics=True,\n",
      "tf32=None,\n",
      "torch_compile=False,\n",
      "torch_compile_backend=None,\n",
      "torch_compile_mode=None,\n",
      "torch_empty_cache_steps=None,\n",
      "torchdynamo=None,\n",
      "tpu_metrics_debug=False,\n",
      "tpu_num_cores=None,\n",
      "use_cpu=False,\n",
      "use_ipex=False,\n",
      "use_legacy_prediction_loop=False,\n",
      "use_liger_kernel=False,\n",
      "use_mps_device=False,\n",
      "warmup_ratio=0.0,\n",
      "warmup_steps=0,\n",
      "weight_decay=0.0,\n",
      ")\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "/usr/local/lib/python3.10/dist-packages/torch/nn/modules/module.py:1762: FutureWarning: `encoder_attention_mask` is deprecated and will be removed in version 4.55.0 for `BertSdpaSelfAttention.forward`.\n",
      "  return forward_call(*args, **kwargs)\n"
     ]
    },
    {
     "data": {
      "text/html": [
       "\n",
       "    <div>\n",
       "      \n",
       "      <progress value='375' max='375' style='width:300px; height:20px; vertical-align: middle;'></progress>\n",
       "      [375/375 05:47, Epoch 3/3]\n",
       "    </div>\n",
       "    <table border=\"1\" class=\"dataframe\">\n",
       "  <thead>\n",
       " <tr style=\"text-align: left;\">\n",
       "      <th>Epoch</th>\n",
       "      <th>Training Loss</th>\n",
       "      <th>Validation Loss</th>\n",
       "      <th>Accuracy</th>\n",
       "    </tr>\n",
       "  </thead>\n",
       "  <tbody>\n",
       "    <tr>\n",
       "      <td>1</td>\n",
       "      <td>1.332000</td>\n",
       "      <td>1.180518</td>\n",
       "      <td>0.478000</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <td>2</td>\n",
       "      <td>1.007800</td>\n",
       "      <td>1.093471</td>\n",
       "      <td>0.504000</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <td>3</td>\n",
       "      <td>0.597900</td>\n",
       "      <td>1.119056</td>\n",
       "      <td>0.538000</td>\n",
       "    </tr>\n",
       "  </tbody>\n",
       "</table><p>"
      ],
      "text/plain": [
       "<IPython.core.display.HTML object>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "/usr/local/lib/python3.10/dist-packages/torch/nn/modules/module.py:1762: FutureWarning: `encoder_attention_mask` is deprecated and will be removed in version 4.55.0 for `BertSdpaSelfAttention.forward`.\n",
      "  return forward_call(*args, **kwargs)\n",
      "/usr/local/lib/python3.10/dist-packages/torch/nn/modules/module.py:1762: FutureWarning: `encoder_attention_mask` is deprecated and will be removed in version 4.55.0 for `BertSdpaSelfAttention.forward`.\n",
      "  return forward_call(*args, **kwargs)\n",
      "/usr/local/lib/python3.10/dist-packages/torch/nn/modules/module.py:1762: FutureWarning: `encoder_attention_mask` is deprecated and will be removed in version 4.55.0 for `BertSdpaSelfAttention.forward`.\n",
      "  return forward_call(*args, **kwargs)\n",
      "/usr/local/lib/python3.10/dist-packages/torch/nn/modules/module.py:1762: FutureWarning: `encoder_attention_mask` is deprecated and will be removed in version 4.55.0 for `BertSdpaSelfAttention.forward`.\n",
      "  return forward_call(*args, **kwargs)\n"
     ]
    },
    {
     "data": {
      "text/html": [
       "\n",
       "    <div>\n",
       "      \n",
       "      <progress value='13' max='13' style='width:300px; height:20px; vertical-align: middle;'></progress>\n",
       "      [13/13 00:02]\n",
       "    </div>\n",
       "    "
      ],
      "text/plain": [
       "<IPython.core.display.HTML object>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "评估结果： {'eval_loss': 1.205830693244934, 'eval_accuracy': 0.56, 'eval_runtime': 2.664, 'eval_samples_per_second': 37.538, 'eval_steps_per_second': 4.88, 'epoch': 3.0}\n",
      "最终评估结果 - accuracy: 0.56\n"
     ]
    }
   ],
   "source": [
    "import random\n",
    "import pandas as pd\n",
    "import numpy as np\n",
    "import evaluate\n",
    "from datasets import Dataset, load_dataset\n",
    "import datasets\n",
    "from IPython.display import display, HTML\n",
    "from transformers import AutoTokenizer, AutoModelForSequenceClassification, TrainingArguments, Trainer\n",
    "\n",
    "def main():\n",
    "    def show_random_elements(dataset, num_examples=10):\n",
    "        assert num_examples <= len(dataset), \"Can't pick more elements than there are in the dataset.\"\n",
    "        picks = []\n",
    "        for _ in range(num_examples):\n",
    "            pick = random.randint(0, len(dataset)-1)\n",
    "            while pick in picks:\n",
    "                pick = random.randint(0, len(dataset)-1)\n",
    "            picks.append(pick)\n",
    "        \n",
    "        df = pd.DataFrame(dataset[picks])\n",
    "        for column, typ in dataset.features.items():\n",
    "            if isinstance(typ, datasets.ClassLabel):\n",
    "                df[column] = df[column].transform(lambda i: typ.names[i])\n",
    "        display(HTML(df.to_html()))\n",
    "   \n",
    "    # 加载数据集，（包含 5 类情感标签的评论数据）\n",
    "    dataset = load_dataset(\"yelp_review_full\")\n",
    "    # 随机展示数据集10条数据，方便直观了解数据结构\n",
    "    show_random_elements(dataset[\"train\"])\n",
    "\n",
    "     # 加载bert的分词器\n",
    "    tokenizer = AutoTokenizer.from_pretrained(\"bert-base-cased\")\n",
    "    # 加载bert模型 ，模型针对 5 分类任务进行了调整\n",
    "    model = AutoModelForSequenceClassification.from_pretrained(\"bert-base-cased\", num_labels=5)\n",
    "\n",
    "    #定义tokenize_function对文本进行分词、填充和截断\n",
    "    def tokenize_function(examples):\n",
    "        # padding=\"max_length\"确保填充到统一长度；max_length=256明确指定最大长度\n",
    "        # return tokenizer(examples[\"text\"], padding=\"max_length\", truncation=True, max_length=256)\n",
    "        return tokenizer(examples[\"text\"], padding=\"max_length\", truncation=True)\n",
    "\n",
    "    #使用map方法批量处理数据集，提高效率\n",
    "    tokenized_datasets = dataset.map(tokenize_function, batched=True)\n",
    "    tokenized_datasets = tokenized_datasets.with_format(\"torch\", columns=[\"input_ids\", \"attention_mask\", \"label\"])\n",
    "    # 修改为使用完整的训练集与测试集去训练，构建小规模训练集（1000 条）和验证集（1000 条）用于快速实验\n",
    "    small_train_dataset = tokenized_datasets[\"train\"].select(range(1000))\n",
    "    small_eval_dataset = tokenized_datasets[\"test\"].select(range(1000))\n",
    "\n",
    "    model_dir = \"models/bert-base-cased-finetune-yelp\"\n",
    "    # Evaluate库提供了一个简单的准确率函数，可以使用evaluate.load函数加载，用于评估准确率\n",
    "    metric = evaluate.load(\"accuracy\")\n",
    "\n",
    "    \"\"\"训练器（Trainer）在训练过程中不会自动评估模型性能。因此，我们需要向训练器传递一个函数来计算和报告指标\n",
    "    Evaluate库提供了一个简单的准确率函数，您可以使用evaluate.load函数加载。接着，调用 compute 函数来计算预测的准确率\n",
    "    在将预测传递给 compute 函数之前，我们需要将 logits 转换为预测值（所有Transformers 模型都返回 logits）\"\"\"\n",
    "    #定义评估指标（准确率），通过compute_metrics函数实现\n",
    "    def compute_metrics(eval_pred):\n",
    "        logits, labels = eval_pred\n",
    "        predictions = np.argmax(logits, axis=-1)\n",
    "        return metric.compute(predictions=predictions, references=labels)\n",
    "    #设置训练参数（输出目录、评估策略、批次大小、训练轮次等）\n",
    "    # logging_steps 默认值为500，根据我们的训练数据和步长，将其设置为30\n",
    "    training_args = TrainingArguments(output_dir=model_dir,\n",
    "                                  eval_strategy=\"epoch\", \n",
    "                                  per_device_train_batch_size=8,\n",
    "                                  num_train_epochs=3,\n",
    "                                  logging_steps=30)\n",
    "    print(\"完整的超参数配置：\", training_args)\n",
    "\n",
    "    trainer = Trainer(\n",
    "        model=model,\n",
    "        args=training_args,\n",
    "        train_dataset=small_train_dataset,\n",
    "        eval_dataset=small_eval_dataset,\n",
    "        compute_metrics=compute_metrics,\n",
    "    )\n",
    "    #使用Trainer API 进行模型训练\n",
    "    trainer.train() # 训练\n",
    "\n",
    "    # 随机选择100个测试集进行评估\n",
    "    tokenized_small_test_dataset = tokenized_datasets[\"test\"].shuffle(seed=64).select(range(100))\n",
    "    eval_results = trainer.evaluate(tokenized_small_test_dataset) # 评估\n",
    "    print(\"评估结果：\", eval_results)\n",
    "    #在测试集上评估模型性能并输出准确率\n",
    "    print(f\"最终评估结果 - accuracy: {eval_results['eval_accuracy']:.2f}\")\n",
    "\n",
    "    # trainer.save_model(model_dir)  # 保存模型\n",
    "    # trainer.save_state()  # 保存训练状态\n",
    "\n",
    "if __name__ == '__main__':\n",
    "    main()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "2c459788-6e5a-478d-bddd-45ce70ad70f9",
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3 (ipykernel)",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.10.12"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
